#pragma once

#include <string>
#include <stdexcept>
#include <memory>
#include <unordered_set>
#include "module_frame.hh"
#include "../../engine/component.hh"

using namespace kratos;

// Implementation for ComponentVersion
class ComponentVersionImpl : public engine::ComponentVersion {
    std::string version_; // version string
    std::string phase_; // The DEV phase
    std::string fullVersion_; // combination of version_ and phase_

public:
    virtual const std::string& getVersion() const override;
    virtual const std::string& getFullVersion() const override;

protected:
    // Sets version
    void setVersion(const std::string& version, const std::string& phase);

    template <typename T>
    friend class ComponentFactoryImpl;
};

// Wrapper of user class to transfer T to be a Component
template <typename T>
class ComponentWrapper : public engine::Component, public T {
    std::string name_; // component name
    engine::ComponentFactory* factory_; // component factory
    ComponentVersionImpl version_; // component version
    using TrackerSet = std::unordered_set<std::ptrdiff_t>;
    TrackerSet trackerSet_; // Tracker set

public:
    // ctor
    ComponentWrapper()
        : factory_(nullptr) {
    }
    // dtor
    virtual ~ComponentWrapper() {
    }
    virtual const std::string& getName() const override {
        return name_;
    }
    virtual ComponentFactory* getFactory() override {
        return factory_;
    }
    virtual bool track(ComponentTracker* tracker) override {
        if (!tracker) {
            return false;
        }
        trackerSet_.insert(reinterpret_cast<std::ptrdiff_t>(tracker));
        return true;
    }
    virtual bool untrack(ComponentTracker* tracker) override {
        if (!tracker) {
            return false;
        }
        trackerSet_.erase(reinterpret_cast<std::ptrdiff_t>(tracker));
        return true;
    }
    virtual void onErase() override {
        for (auto ptr : trackerSet_) {
            auto tracker = reinterpret_cast<ComponentTracker*>(ptr);
            tracker->onVanish(name_);
        }
        if (!trackerSet_.empty()) {
            trackerSet_.clear();
        }
    }
    virtual const ComponentVersion& getVersion() override {
        return getVersionImpl();
    }

protected:
    // Sets component name
    void setName(const std::string& name) {
        name_ = name;
    }
    // Sets component factory
    void setFactory(engine::ComponentFactory* factory) {
        factory_ = factory;
    }
    // Returns component version
    ComponentVersionImpl& getVersionImpl() {
        return version_;
    }

    template <typename Type>
    friend class ComponentFactoryImpl;
};

// Implementation for ComponentFactory
template <typename T>
class ComponentFactoryImpl : public engine::ComponentFactory {
public:
    virtual ~ComponentFactoryImpl() {}
    engine::Component* create(const std::string& name, const std::string& version, const std::string& phase) {
        auto ptr = corelib::allocate<ComponentWrapper<T>>();
        if (!ptr) {
            return nullptr;
        }        
        ptr->setName(name);
        ptr->setFactory(this);
        ptr->getVersionImpl().setVersion(version, phase);
        try {
            auto dtype = dynamic_cast<Component*>(ptr);
            if (dtype) {
                return dtype;
            } else {
                errorlog << "Create component failed, type:" << typeid(T).name() << endlog;
            }
        } catch (std::exception& e) {
            errorlog << "Create component failed, type:" << typeid(T).name() << ", exception reason:" << e.what() << endlog;
        }
        return nullptr;
    }
    virtual void destroy(engine::Component* component) override {
        if (component) {
            corelib::dispose(dynamic_cast<ComponentWrapper<T>*>(component));
        } else {
            errorlog << "Destroy component nullptr, type:" << typeid(T).name() << endlog;
        }
    }
};

#define ComponentFactoryRef(type) (*corelib::Singleton<ComponentFactoryImpl<type>>::instance())
#define ComponentFactorySingleton(type) kratos::corelib::Singleton<ComponentFactoryImpl<type>>

// Global function to unregister a component
// @param name component name
// @retval true
// @retval false fail
static inline bool unregisterComponent(const std::string& name) {
    return ModuleRef.getComponentManager().unregisterComponent(name);
}

// Global function to track component
// @param name component name
// @param tracker component lifecycle tracker
// @retval true
// @retval false fail
static inline bool trackComponent(const std::string& name, engine::ComponentTracker* tracker) {
    auto sharedPtr = ModuleRef.getComponentManager().get(name);
    if (!sharedPtr) {
        return false;
    }
    return sharedPtr->track(tracker);
}

// Global function to untrack component
// @param name component name
// @param tracker component lifecycle tracker
// @retval true
// @retval false fail
static inline bool untrackComponent(const std::string& name, engine::ComponentTracker* tracker) {
    auto sharedPtr = ModuleRef.getComponentManager().get(name);
    if (!sharedPtr) {
        return false;
    }
    return sharedPtr->untrack(tracker);
}

// Global function to query component
// @param name component name
// @param [IN OUT] version required version
// @param tracker component lifecycle tracker
// @return A std::shared_ptr within T
template <typename T>
static inline std::shared_ptr<T> getComponent(const std::string& name, std::string& version, ComponentTracker* tracker = nullptr) {
    auto sharedPtr = ModuleRef.getComponentManager().get(name);
    if (!sharedPtr) {
        return std::shared_ptr<T>();
    }
    if (tracker) {
        sharedPtr->track(tracker);
    }
    auto& realVersion = sharedPtr->getVersion().getVersion();
    if (!version.empty() && (realVersion != version)) {
        version = realVersion;
        errorlog << "Required component version is unsatisfied, required version:" << version
            << ", component version:" << realVersion << endlog;
        return std::shared_ptr<T>();
    } else {
        version = realVersion;
    }
    try {
        return std::dynamic_pointer_cast<T>(sharedPtr);
    } catch (std::exception& e) {
        errorlog << "getComponent failed when dynamic cast to " << typeid(T).name() <<
            ", exception reason:" << e.what() << endlog;
        if (tracker) {
            sharedPtr->untrack(tracker);
        }
    }
    return std::shared_ptr<T>();
}

// Global function to register a user defined class to be a component
// @param name component name
// @param version component version
// @param phase the DEV phase
// @return A std::shared_ptr within T
template <typename T>
static inline std::shared_ptr<T> registerComponent(const std::string& name, const std::string& version, const std::string& phase = "") {
    auto obj = ComponentFactoryRef(T).create(name, version, phase);
    if (!obj) {
        return std::shared_ptr<T>();
    }
    if (!ModuleRef.getComponentManager().registerComponent(obj)) {
        errorlog << "Expose component faild, name:" << name << ", type:" << typeid(T).name() << endlog;
        ComponentFactoryRef(T).destroy(obj);
        return std::shared_ptr<T>();
    }
    std::string requiredVersion(version);
    return getComponent<T>(name, requiredVersion);
}

